package model

import (
	"crypto/md5"
	"encoding/base32"
	"errors"
	"math/rand"
	"time"

	"github.com/dailing/levlog"
	"github.com/dgrijalva/jwt-go"
	_ "github.com/go-sql-driver/mysql"
)

const (
	AccessLevelNormalUser = 0
	AccessLevelMnager     = 100
	AccessLevelRoot       = 200
)

var seededRand = rand.New(
	rand.NewSource(time.Now().UnixNano()))

func randStringGen(length int) string {
	const charset = "abcdefghijklmnopqrstuvwxyz" +
		"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, length)
	for i := range b {
		b[i] = charset[seededRand.Intn(len(charset))]
	}
	return string(b)
}

type User struct {
	Id          int64  `json:"id"`
	Name        string `xorm:"Unique" json:"name"`
	Salt        string `json:"-"`
	Psw         string `json:"-"`
	Gender      int    `json:"gender"`
	Insitude    string `json:"inditude"`
	AccessLevel int64  `xorm:"default 0" json:"-"`
	RawPsw      string `xorm:"-" json:"psw"`
}

type UserToken struct {
	UserName    string
	UserID      int64
	AccessLevel int64
	ExpireAt    time.Time
	jwt.StandardClaims
}

func (t *UserToken) getToken() string {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, t)
	ss, err := token.SignedString([]byte("Secret"))
	levlog.E(err)
	return ss
}

func ParseToken(tokenString string) (*UserToken, error) {
	pt := &UserToken{}
	token, err := jwt.ParseWithClaims(tokenString, pt, func(token *jwt.Token) (interface{}, error) {
		return []byte("Secret"), nil
	})

	if _, ok := token.Claims.(*UserToken); ok && token.Valid {
		if pt.ExpireAt.Before(time.Now()) {
			return nil, errors.New("Data expired")
		}
	} else {
		levlog.E(err)
		return nil, err
	}
	return pt, nil
}

func (u *User) TableName() string { return "user" }

func (u *User) GetToken() string {
	claims := &UserToken{
		u.Name,
		u.Id,
		u.AccessLevel,
		time.Now().Add(time.Hour * 24 * 7),
		jwt.StandardClaims{
			Issuer: "SingAppServer",
		},
	}
	return claims.getToken()
}

func (u *User) Insert() error {
	if u.Name == "" {
		return errors.New("no user name")
	}
	if u.RawPsw == "" {
		return errors.New("no pass word")
	}
	u.Id = 0
	levlog.Info("Adding User ", u)
	u.Salt = randStringGen(30)
	u.Psw = PswHash(u.RawPsw, u.Salt)
	if _, err := engine.Insert(u); err != nil {
		levlog.E(err)
		return err
	}
	return nil
}

func AddManger(name, psw string) error {
	user := User{
		Name:        name,
		RawPsw:      psw,
		AccessLevel: 100,
	}
	return user.Insert()
}

func AddUser(name, psw, insitude string) error {
	user := User{
		Name:     name,
		RawPsw:   psw,
		Insitude: insitude,
	}
	return user.Insert()
}

func PswHash(psw, salt string) string {
	levlog.Trace("HASH:", psw, ",", salt)
	sum := md5.Sum([]byte(salt + psw))
	sumstr := base32.HexEncoding.EncodeToString(sum[:])
	return sumstr
}

func (u *User) Check() (err error) {
	if u.RawPsw == "" || u.Name == "" {
		return errors.New("no paw or name")
	}
	ppsw := u.RawPsw
	*u = User{
		Name: u.Name,
	}
	if ok, err := engine.Get(u); !ok || err != nil {
		levlog.E(err)
		return errors.New("no such user")
	}
	pswCheck := PswHash(ppsw, u.Salt)
	if pswCheck != u.Psw {
		return errors.New("error password")
	}
	return nil
}

func (u *User) Get() (err error) {
	if u.Id <= 0 {
		return errors.New("id not correct")
	}
	if ok, err := engine.Get(u); !ok || err != nil {
		levlog.E(err)
		return errors.New("no such user")
	}
	return nil
}

func (u *User) Update() error {
	_, err := engine.
		Id(u.Id).
		Cols("wei_xin", "gender").
		Update(u)
	levlog.E(err)
	return err
}
